home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / HippoDraw / HippoDrawSrc1.1 / Hippo.subproj / DrawDocument.m < prev    next >
Encoding:
Text File  |  1992-04-25  |  36.0 KB  |  1,262 lines

  1. /* DrawDocument.m    by Paul Hegarty, NeXT, Inc.
  2.  * Delegate to drawing Window.
  3.  * Modified to support HippoDraw
  4.  *
  5.  * Copyright (C)  1991  The Board of Trustees of
  6.  * The Leland Stanford Junior University.  All Rights Reserved.
  7.  */
  8.  
  9. #import "DrawDocument.h"
  10.  
  11. const char DrawDocument_h_rcsid[] = DRAWDOCUMENT_H_ID;
  12. const char DrawDocument_m_rcsid[]= "$Id: DrawDocument.m,v 1.38 1992/04/13 20:43:55 pfkeb Rel $";
  13.  
  14. #import "HDraw.h"
  15. #import "HGraphicView.h"
  16. #import "DrawPageLayout.h"
  17. #import "GridView.h"
  18. #import "InspectTuple.h"
  19. #import "SyncScrollView.h"
  20. #import "Ruler.h"
  21. #import "hippo.h"
  22. #import <appkit/Application.h>
  23. #import <appkit/Cursor.h>
  24. #import <appkit/Listener.h>
  25. #import <appkit/Matrix.h>
  26. #import <appkit/Pasteboard.h>
  27. #import <appkit/PrintInfo.h>
  28. #import <appkit/SavePanel.h>
  29. #import <appkit/Speaker.h>
  30. #import <appkit/nextstd.h>
  31. #import <appkit/publicWraps.h>
  32. #import <objc/List.h>
  33. #import <objc/hashtable.h>    /* for NXCopyStringBuffer() */
  34. #import <zone.h>
  35. #import <string.h>
  36. #import <libc.h>
  37. #import <mach.h>
  38. #import <sys/types.h>
  39.  
  40.  
  41. #define Notify(title, msg) NXRunAlertPanel(title, msg, "OK", NULL, NULL)
  42.  
  43. #define NEW_DRAW_VERSION 184
  44.  
  45. @implementation DrawDocument
  46. /*
  47.  * This class is used to keep track of a Draw document.
  48.  *
  49.  * Its view and window instance variables keep track of the GraphicView
  50.  * comprising the document as well as the window it is in.
  51.  * The printInfo instance variable is used to allow the user to control
  52.  * how the printed page is printed.  It is an instance of a PrintInfo
  53.  * object (which is edited via the PageLayout and PrintPanels).
  54.  * The listener is used to allow the user to drag an icon representing
  55.  * a PostScript or TIFF file into the document.  The iconPathList is the
  56.  * list of files which was last dragged into the document.
  57.  * The name and directory specify where the document is to be saved.
  58.  * haveSavedDocument keeps track of whether a disk file is associated
  59.  * with the document yet (i.e. if it has ever been saved).
  60.  *
  61.  * The DrawDocument class's responsibilities:
  62.  *
  63.  * 1. Manage the window (including the scrolling view) which holds the
  64.  *    document's GraphicView.  This includes constraining the resizing of
  65.  *    the window so that it never becomes larger than the GraphicView, and
  66.  *    ensuring that if the window contains an unsaved document and the user
  67.  *    tries to close it, the user gets an opportunity to save her changes.
  68.  * 2. Handle communication with the Workspace Manager which allows icons
  69.  *    for PostScript and TIFF files to be dragged into the document window
  70.  *    and be assimilated into the document.
  71.  * 3. Saving the document to a disk file.
  72.  * 4. Provide an external interface to saving the contents of the GraphicView
  73.  *    as a PostScript or TIFF file.
  74.  */
  75.  
  76. #define MIN_WINDOW_WIDTH 50.0
  77. #define MIN_WINDOW_HEIGHT 75.0
  78. #define SCROLLVIEW_BORDER NX_NOBORDER
  79.  
  80. static NXRect *calcFrame(id printInfo, NXRect *viewRect)
  81. /*
  82.  * Calculates the size of the page the user has chosen minus its margins.
  83.  */
  84. {
  85.     float lm, rm, bm, tm;
  86.     const NXRect *paperRect;
  87.  
  88.     viewRect->origin.x = viewRect->origin.y = 0.0;
  89.     paperRect = [printInfo paperRect];
  90.     [printInfo getMarginLeft:&lm right:&rm top:&tm bottom:&bm];
  91.     viewRect->size = paperRect->size;
  92.     viewRect->size.width -= lm + rm;
  93.     viewRect->size.height -= tm + bm;
  94.  
  95.     return viewRect;
  96. }
  97.  
  98. static void getContentSizeForView(id view, NXSize *contentSize)
  99. /*
  100.  * Calculates the size of the window's contentView by accounting for the
  101.  * existence of the ScrollView around the GraphicView.  No scrollers are
  102.  * assumed since we are interested in the minimum size need to enclose
  103.  * the entire view and, if the entire view is visible, we don't need
  104.  * scroll bars!
  105.  */
  106. {
  107.     NXRect viewFrame;
  108.  
  109.     [view getFrame:&viewFrame];
  110.     [SyncScrollView getFrameSize:contentSize
  111.           forContentSize:&viewFrame.size
  112.            horizScroller:YES vertScroller:YES
  113.           borderType:SCROLLVIEW_BORDER];
  114. }
  115.  
  116. #define WINDOW_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_FLAGSCHANGEDMASK)
  117.  
  118. static id createWindowFor(id view, NXRect *windowContentRect)
  119. /*
  120.  * Creates a window for the specified view.
  121.  * If windowContentRect is NULL, then a window big enough to fit the whole
  122.  * view is created (unless that would be too big to comfortably fit on the
  123.  * screen, in which case a smaller window may be allocated).
  124.  * If windowContentRect is not NULL, then it is used as the contentView of
  125.  * the newly created window.
  126.  *
  127.  * setMiniwindowIcon: sets the name of the bitmap which will be used in
  128.  * the miniwindow of the window (i.e. when the window is miniaturized).
  129.  * The icon "drawdoc" was defined in InterfaceBuilder (take a look in
  130.  * the icon suitcase).
  131.  */
  132. {
  133.     NXSize screenSize;
  134.     id scrollView, window;
  135.     NXRect defaultWindowContentRect;
  136.  
  137.     if (!windowContentRect) {
  138.     windowContentRect = &defaultWindowContentRect;
  139.     getContentSizeForView(view, &windowContentRect->size);
  140.     [NXApp getScreenSize:&screenSize];
  141.     if (windowContentRect->size.width > screenSize.width / 2.0) {
  142.         windowContentRect->size.width = floor(screenSize.width / 2.0);
  143.     }
  144.     if (windowContentRect->size.height > screenSize.height - 300.0) {
  145.         windowContentRect->size.height = screenSize.height - 300.0;
  146.     }
  147.     windowContentRect->origin.x = 200.0; /*screenSize.width - 75.0 - windowContentRect->size.width;*/
  148.     windowContentRect->origin.y = floor(screenSize.height - windowContentRect->size.height-24.0);
  149.     }
  150.  
  151.     window = [[Window allocFromZone:[view zone]] initContent:windowContentRect
  152.               style:NX_TITLEDSTYLE
  153.                 backing:(InMsgPrint ? NX_NONRETAINED : NX_BUFFERED)
  154.              buttonMask:NX_ALLBUTTONS
  155.               defer:(InMsgPrint ? NO : YES)];
  156.  
  157.     scrollView = [[SyncScrollView allocFromZone:[view zone]] initFrame:windowContentRect];
  158.     [scrollView setRulerClass:[Ruler class]];
  159.     [scrollView setRulerOrigin:UpperLeft];
  160.     [scrollView setRulerWidths:[Ruler width] :[Ruler width]];
  161.     [scrollView setVertScrollerRequired:YES];
  162.     [scrollView setHorizScrollerRequired:YES];
  163.     [scrollView setBorderType:SCROLLVIEW_BORDER];
  164.     [scrollView setDocView:view];
  165.     [window setContentView:scrollView];
  166.     [window addToEventMask:WINDOW_MASK];
  167.     [window makeFirstResponder:view];
  168.     [window setMiniwindowIcon:"hippo"];
  169.  
  170.     return window;
  171. }
  172.  
  173. /* Very private methods needed by factory methods */
  174.  
  175. + backupOldDrawDocument:(const char *)file
  176. /*
  177. * We do this because we are still beta-testing the new version.
  178. */
  179. {
  180.     int version;
  181.     volatile BOOL isOld = NO;
  182.     NXStream *volatile stream;
  183.     char *extension, *dash;
  184.     NXTypedStream *volatile ts = NULL;
  185.     char buffer[MAXPATHLEN+1];
  186.  
  187.     NX_DURING
  188.     stream = NXMapFile(file, NX_READONLY);
  189.     if (stream) ts = NXOpenTypedStream(stream, NX_READONLY);
  190.     if (ts) {
  191.         NXReadType(ts, "i", &version);
  192.         if (version != NEW_DRAW_VERSION) isOld = YES;
  193.     }
  194.     NX_HANDLER
  195.     NX_ENDHANDLER
  196.  
  197.     if (ts) NXCloseTypedStream(ts);
  198.     if (stream) NXClose(stream);
  199.  
  200.     if (isOld) {
  201.     strcpy(buffer, file);
  202.     extension = strrchr(buffer, '.');
  203.     if (extension && !strcmp(extension, ".hdraw")) {
  204.         dash = strrchr(buffer, '-');
  205.         if (!dash || strcmp(dash, "-1.0.hdraw")) strcpy(extension, "-1.0.hdraw");
  206.     } else {
  207.         strcat(buffer, "-1.0");
  208.     }
  209.     link(file, buffer);
  210.     }
  211.  
  212.     return self;
  213. }
  214.  
  215. - (BOOL)loadDocument:(NXStream *)stream frameSize:(NXRect *)frame
  216. /*
  217.  * Loads an archived document from the specified filename.
  218.  * Loads the window frame specified in the archived document into the
  219.  * frame argument (if the frame argument is NULL, then the frame in
  220.  * the archived document is ignored).  Returns YES if the document
  221.  * has been successfully loaded, NO otherwise.  Note that this method
  222.  * destroys the receiving document, so use with extreme care
  223.  * (essentially, this should only be called when a new document is
  224.  * being created or an existing one is being reverted to its form
  225.  * on disk).
  226.  *
  227.  * An NX_DURING handler is needed around the NXTypedStream operations because
  228.  * if the user has asked that a bogus file be opened, the NXTypedStream will
  229.  * raise an error.  To handle the error, the NXTypedStream must be closed.
  230.  */
  231. {
  232.     int version;
  233.     NXRect dummyRect;
  234.     volatile BOOL retval = YES;
  235.     NXTypedStream *volatile ts = NULL;
  236.  
  237.     if (!frame) frame = &dummyRect;
  238.  
  239.     NX_DURING
  240.     ts = NXOpenTypedStream(stream, NX_READONLY);
  241.     if (ts) {
  242.         NXSetTypedStreamZone(ts, [self zone]);
  243.         NXReadType(ts, "i", &version);    /* historical */
  244.         printInfo = NXReadObject(ts);
  245.         NXReadRect(ts, frame);
  246.         view = NXReadObject(ts);
  247.     } else {
  248.         retval = NO;
  249.     }
  250.     NX_HANDLER
  251.     retval = NO;
  252.     NX_ENDHANDLER
  253.  
  254.     if (ts) NXCloseTypedStream(ts);
  255.  
  256.     return retval;
  257. }
  258.  
  259. /* Factory methods */
  260.  
  261. /*
  262.  * We reuse zones since it doesn't cost us anything to have a
  263.  * zone lying around (e.g. if we open ten documents at the start
  264.  * then don't use 8 of them for the rest of the session, it doesn't
  265.  * cost us anything except VM (no real memory cost)), and it is
  266.  * risky business to go around NXDestroy()'ing zones since if
  267.  * your application accidentally allocates some piece of global
  268.  * data into a zone that gets destroyed, you could have a pointer
  269.  * to freed data on your hands!  We use the List object since it
  270.  * is so easy to use (which is okay as long as 'id' remains a
  271.  * pointer just like (NXZone *) is a pointer!).
  272.  *
  273.  * Note that we don't implement alloc and allocFromZone: because
  274.  * we create our own zone to put ourselves in.  It is generally a
  275.  * good idea to "notImplemented:" those methods if you do not allow
  276.  * an object to be alloc'ed from an arbitrary zone (other examples
  277.  * include Application and all of the Application Kit panels
  278.  * (which allocate themselves into their own zone).
  279.  */
  280.  
  281. static id zoneList = nil;
  282.  
  283. + (NXZone *)newZone
  284. {
  285.     if (!zoneList || ![zoneList count]) {
  286.     return NXCreateZone(vm_page_size, vm_page_size, YES);
  287.     } else {
  288.     return (NXZone *)[zoneList removeLastObject];
  289.     }
  290. }
  291.  
  292. + (void)reuseZone:(NXZone *)aZone
  293. {
  294.     if (!zoneList) zoneList = [List new];
  295.     [zoneList addObject:(id)aZone];
  296.     NXNameZone(aZone, "Unused");
  297. }
  298.  
  299. + allocFromZone:(NXZone *)aZone
  300. {
  301.     return [self notImplemented:@selector(allocFromZone:)];
  302. }
  303.  
  304. + alloc
  305. {
  306.     return [self notImplemented:@selector(alloc)];
  307. }
  308.  
  309. /* Creation methods */
  310.  
  311. + new
  312. /*
  313.  * Creates a new, empty, document.
  314.  *
  315.  * Creates a PrintInfo object; creates a view whose size depends on the
  316.  * default PrintInfo created; creates a window for that view; sets self
  317.  * as the window's delegate; orders the window front; registers the window
  318.  * with the Workspace Manager.  Note that the default margins are set
  319.  * to 1/2 inch--that's more appropriate for a draw program than 1 or 1.25
  320.  * inches.
  321.  */
  322. {
  323.     NXZone *zone;
  324.     NXRect frameRect;
  325.     NXPoint top;
  326.  
  327.     zone = [self newZone];
  328.     self = [super allocFromZone:zone];
  329.     [self registerForServicesMenu];
  330.     printInfo = [PrintInfo new];
  331.     [printInfo setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0];
  332.     calcFrame(printInfo, &frameRect);
  333.     view = [[HGraphicView allocFromZone:zone] initFrame:&frameRect];
  334.     [view setClipping:NO];            /* since it is in a ClipView */
  335.     [view setGridSpacing:10];
  336.     [view setGridVisible:YES];
  337.     window = createWindowFor(view, NULL);
  338.     [window setDelegate:self];
  339.     top.x = 0;
  340.     top.y = frameRect.size.height;
  341.     [view scrollPoint:&top];
  342.     [self resetScrollers];
  343.     [self setName:NULL andDirectory:NULL];
  344.     [window makeKeyAndOrderFront:self];
  345.     [self registerWindow];
  346.  
  347.     return self;
  348. }
  349.  
  350. + newFromStream:(NXStream *)stream
  351. /*
  352.  * Creates a new document from what is in the passed stream.
  353.  */
  354. {
  355.     NXZone *zone;
  356.     NXRect contentViewFrame, frameRect;
  357.     NXPoint top;
  358.  
  359.     zone = [self newZone];
  360.     self = [super allocFromZone:zone];
  361.     [self registerForServicesMenu];
  362.     if (stream && [self loadDocument:stream frameSize:&contentViewFrame]) {
  363.     window = createWindowFor(view, &contentViewFrame);
  364.     [window setDelegate:self];
  365.     [view getFrame:&frameRect];
  366.     top.x = 0;
  367.     top.y = frameRect.size.height;
  368.     [view scrollPoint:&top];
  369.     [self resetScrollers];
  370.     if (!InMsgPrint) {
  371.         [window makeKeyAndOrderFront:self];
  372.         [self registerWindow];
  373.     }
  374.     haveSavedDocument = YES;
  375.     return self;
  376.     } else {
  377.     Notify("Open Draw Document", "I/O error.  Can't open file.");
  378.     [self free];
  379.     return nil;
  380.     }
  381. }
  382.  
  383. + newFromFile:(const char *)file
  384. /*
  385.  * Opens an existing document from the specified file.
  386.  */
  387. {
  388.     NXStream           *stream;
  389.  
  390.     [self backupOldDrawDocument:file];
  391.     stream = NXMapFile(file, NX_READONLY);
  392.     if (stream) {
  393.     if (self = [self newFromStream:stream]) [self setName:file];
  394.     NXClose(stream);
  395.     return self;
  396.     } else {
  397.     return nil;
  398.     }
  399. }
  400. - openTupleFile:(const char *)path
  401. {
  402.     id        inspectTuple;
  403.     BOOL    refFlag;
  404.     int        irc;
  405.     
  406.     if ( hDraw == nil ) {
  407.     hDraw = NXGetNamedObject("HDrawInstance", NXApp);
  408.     }
  409.     inspectTuple = [hDraw inspectTuple];
  410.     irc = NXRunAlertPanel( "Open",
  411.         "Import tuple by reference?",
  412.         "Yes", "No", NULL);
  413.     refFlag = (irc == NX_ALERTDEFAULT) ? YES : NO ;
  414.     irc = [inspectTuple openTupleFile:path by:refFlag];
  415.     if ( irc == HD_CANCEL ) {
  416.         return self;
  417.     }
  418.     if ( irc == HD_REPLACE ) {
  419.         [inspectTuple bindDisplays];
  420.     }
  421.     [view reDrawPlot];
  422.     return self;
  423. }
  424. - free
  425. {
  426.     [printInfo free];
  427.     [window free];
  428.     NX_FREE(name);
  429.     NX_FREE(directory);
  430.     NX_FREE(iconPathList);
  431.     [[self class] reuseZone:[self zone]];
  432.     return [super free];
  433. }
  434.  
  435. /* Services menu support methods. */
  436.  
  437. /* Services menu registrar */
  438.  
  439. - registerForServicesMenu
  440. {
  441.     static BOOL registered = NO;
  442.     const char *validSendTypes[2];
  443.  
  444.     if (!registered) {
  445.     registered = YES;
  446.     validSendTypes[0] = NXFilenamePboardType;
  447.     validSendTypes[1] = NULL;
  448.     [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:NULL];
  449.     }
  450.  
  451.     return self;
  452. }
  453.  
  454. - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
  455. /*
  456.  * Services menu support.
  457.  * We are a valid requestor if the send type is filename
  458.  * and there is no return data from the request.
  459.  */
  460. {
  461.     return (haveSavedDocument && sendType == NXFilenamePboardType && (!returnType || !*returnType)) ? self : nil;
  462. }
  463.  
  464. - writeSelectionToPasteboard:pboard types:(NXAtom *)types
  465. /*
  466.  * Services menu support.
  467.  * Here we are asked by the Services menu mechanism to supply
  468.  * the filename (which we said we were a valid requestor for
  469.  * in the above method).
  470.  */
  471. {
  472.     int save;
  473.  
  474.     if (haveSavedDocument) {
  475.     while (types && *types) if (*types == NXFilenamePboardType) break; else types++;
  476.     if (types && *types) {
  477.         if ([view isDirty]) {
  478.         save = NXRunAlertPanel("Service", "Do you wish to save this document before your request is serviced?", "Save", "Don't Save", NULL);
  479.         if (save == NX_ALERTDEFAULT) [self save];
  480.         }
  481.         [pboard declareTypes:&NXFilenamePboardType num:1 owner:self];
  482.         [pboard writeType:NXFilenamePboardType data:[self filename] length:strlen([self filename])+1];
  483.         return self;
  484.     }
  485.     }
  486.  
  487.     return nil;
  488. }
  489.  
  490. /* Other methods. */
  491.  
  492. - loadImageFile:(const char *)file at:(const NXPoint *)p allowAlpha:(BOOL)alphaOk
  493. /*
  494.  * Maps in the specified file and asks the view to load the PostScript in
  495.  * from the resulting NXStream.  The PostScript image will be centered at
  496.  * the point p (in the GraphicView's coordinate system).  This is called
  497.  * from the icon-dragging mechanism (icons dragged from the Workspace into
  498.  * the document--see registerWindow).
  499.  */
  500. {
  501.     NXStream *stream = NXMapFile(file, NX_READONLY);
  502.     [view loadImageFromStream:stream at:p allowAlpha:alphaOk];
  503.     NXClose(stream);
  504.     return self;
  505. }
  506.  
  507. - resetScrollers
  508. /*
  509.  * Checks to see if the new window size is too large.
  510.  * Called whenever the page layout (either by user action or
  511.  * by the opening or reverting of a file) is changed or
  512.  * the user resizes the window.
  513.  */
  514. {
  515.     id scrollView;
  516.     NXSize contentSize;
  517.     NXRect contentRect, windowFrame;
  518.  
  519.     if (window) {
  520.     [window getFrame:&windowFrame];
  521.     [[window class] getContentRect:&contentRect
  522.               forFrameRect:&windowFrame
  523.                  style:[window style]];
  524.     scrollView = [window contentView];
  525.     getContentSizeForView(view, &contentSize);
  526.     if ([scrollView horizontalRulerIsVisible]) contentSize.height += [Ruler width];
  527.     if ([scrollView verticalRulerIsVisible]) contentSize.width += [Ruler width];
  528.     if (contentRect.size.width >= contentSize.width || contentRect.size.height >= contentSize.height) {
  529.         contentSize.width = MIN(contentRect.size.width, contentSize.width);
  530.         contentSize.height = MIN(contentRect.size.height, contentSize.height);
  531.         [window sizeWindow:contentSize.width :contentSize.height];
  532.     }
  533.     }
  534.  
  535.     return self;
  536. }- view
  537. /*
  538.  * Returns the GraphicView associated with this document.
  539.  */
  540. {
  541.     return view;
  542. }
  543.  
  544. - printInfo
  545. {
  546.     return printInfo;
  547. }
  548.  
  549. /* Target/Action methods */
  550.  
  551. - changeLayout:sender
  552. /*
  553.  * Puts up a PageLayout panel and allows the user to pick a different
  554.  * size paper to work on.  After she does so, the view is resized to the
  555.  * new paper size.
  556.  * Since the PrintInfo is effectively part of the document, we dirty
  557.  * the view (by performing the dirty method).
  558.  */
  559. {
  560.     NXRect frame;
  561.  
  562.     if ( hDraw == nil ) {
  563.     hDraw = NXGetNamedObject("HDrawInstance", NXApp);
  564.     }
  565.     if ([[hDraw pageLayout] runModal] == NX_OKTAG) {
  566.     calcFrame(printInfo, &frame);
  567.     [view sizeTo:frame.size.width :frame.size.height];
  568.     [self resetScrollers];
  569.     [view display];
  570.     [view dirty];
  571.     }
  572.  
  573.     return self;
  574. }
  575. - getPageFrame:(NXRect *)frame
  576. {
  577.     calcFrame(printInfo, frame);
  578.     return self;
  579. }
  580. - changeGrid:sender
  581. /*
  582.  * Changes the grid by putting up a modal panel asking the user what
  583.  * she wants the grid to look like.
  584.  */
  585. {
  586.     if ( hDraw == nil ) {
  587.     hDraw = NXGetNamedObject("HDrawInstance", NXApp);
  588.     }
  589.     [[hDraw gridInspector] runModalForGraphicView:view];
  590.     return self;
  591. }
  592.  
  593. - close:sender
  594. {
  595.     [window performClose:self];
  596.     return self;
  597. }
  598.  
  599. - save:sender
  600. /*
  601.  * Saves the file.  If this document has never been saved to disk,
  602.  * then a SavePanel is put up to ask the user what file name she
  603.  * wishes to use to save the document.
  604.  */
  605. {
  606.     id savepanel;
  607.  
  608.     if (!haveSavedDocument) {
  609.     if ( hDraw == nil ) {
  610.         hDraw = NXGetNamedObject("HDrawInstance", NXApp);
  611.     }
  612.     savepanel = [hDraw saveAsPanel];
  613.     if ([savepanel runModalForDirectory:directory file:name]) {
  614.         [self setName:[savepanel filename]];
  615.     } else {
  616.         return self;
  617.     }
  618.     }
  619.  
  620.     [self save];
  621.  
  622.     return self;
  623. }
  624.  
  625. - saveAs:sender
  626. {
  627.     [view dirty];
  628.     haveSavedDocument = NO;
  629.     return [self save:sender];
  630. }
  631.  
  632. - changeSaveType:sender
  633. /*
  634.  * Called by the SavePanel accessory view whenever the user chooses
  635.  * a different type of file to save to.  The window of the sender
  636.  * is, of course, the SavePanel itself.  setRequiredFileType: does
  637.  * not affect the SavePanel while it is running.  It only has effect
  638.  * when the user has chosen a file, and the SavePanel ensures that it
  639.  * has the correct extension by adding it if it doesn't have it already.
  640.  * This message gets here via the Responder chain from the SavePanel.
  641.  */
  642. {
  643.     switch ([sender selectedRow]) {
  644.     case 0: [[sender window] setRequiredFileType:"hdraw"]; break;
  645.     case 1: [[sender window] setRequiredFileType:"eps"]; break;
  646.     case 2: [[sender window] setRequiredFileType:"tiff"]; break;
  647.     case 3: [[sender window] setRequiredFileType:"hippo"]; break;
  648.     }
  649.     return self;
  650. }
  651.  
  652. - saveTo:sender
  653. /*
  654.  * This takes the document and saves it as a Draw document file, PostScript
  655.  * file, or TIFF file.  If the document type chosen is Draw document, then
  656.  * this saves the file, but DOES NOT make that file the currently edited
  657.  * file (this makes it easy to save your document elsewhere as a backup
  658.  * and keep on going in the current document).
  659.  *
  660.  * If PostScript or TIFF is selected, then the document is written out
  661.  * in the appropriate format.  In the case of PostScript and TIFF, the
  662.  * actual saving is done using the more general method saveAs:using:.
  663.  */
  664. {
  665.     id savepanel;
  666.     const char *file;
  667.     char *type, *savedName, *savedDirectory;
  668.     BOOL viewIsDirty, reallyHaveSavedDocument;
  669.     char buffer[MAXPATHLEN+1];
  670.  
  671.     strcpy(buffer, name);
  672.     type = strrchr(buffer, '.');
  673.     if (type) *type = '\0';
  674.     if ( hDraw == nil ) {
  675.     hDraw = NXGetNamedObject("HDrawInstance", NXApp);
  676.     }
  677.     savepanel = [hDraw saveToPanel];
  678.     if (![savepanel runModalForDirectory:directory file:buffer]) return self;
  679.  
  680.     file = [savepanel filename];
  681.     type = strrchr(file, '.');
  682.     if (type) {
  683.     if (!strcmp(type, ".eps")) {
  684.         [self saveTo:file using:@selector(writePSToStream:)];
  685.     } else if (!strcmp(type, ".tiff")) {
  686.         [self saveTo:file using:@selector(writeTIFFToStream:)];
  687.     } else if (!strcmp(type, ".hdraw")) {
  688.         viewIsDirty = [view isDirty];    /* is it really dirty? */
  689.         [view dirty];            /* temporarily dirty it */
  690.         reallyHaveSavedDocument = haveSavedDocument;
  691.         savedName = name;            /* save current name */
  692.         savedDirectory = directory;        /* save current directory */
  693.         name = NULL; directory = NULL;    /* clear current filename */
  694.         [self setName:file];        /* temporarily change name */
  695.         [self save];            /* save, then restore name */
  696.         [self setName:savedName andDirectory:savedDirectory];
  697.         if (viewIsDirty) [view dirty];    /* restore correct dirtiness */
  698.         haveSavedDocument = reallyHaveSavedDocument;
  699.     } else if ( !strcmp(type, ".hippo") ) {
  700.         [view saveAsExportFile:file];
  701.     }
  702.     }
  703.  
  704.     return self;
  705. }
  706.  
  707.  
  708. - revertToSaved:sender
  709. /*
  710.  * Revert the document back to what is on the disk.
  711.  */ 
  712. {
  713.     NXStream *stream;
  714.     NXRect viewFrame, visibleRect;
  715.  
  716.     if (!haveSavedDocument || ![view isDirty] || (NXRunAlertPanel("Revert", "%s has been edited.  Are you sure you want to undo changes?", "Revert", "Cancel", NULL, name) != NX_ALERTDEFAULT)) {
  717.     return self;
  718.     }
  719.  
  720.     [view getVisibleRect:&visibleRect];
  721.     [window endEditingFor:self];
  722.  
  723.     stream = NXMapFile([self filename], NX_READONLY);
  724.     if (stream && [self loadDocument:stream frameSize:NULL]) {
  725.     [[[window contentView] setDocView:view] free];
  726.     calcFrame(printInfo, &viewFrame);
  727.  
  728.     [view sizeTo:viewFrame.size.width :viewFrame.size.height];
  729.     [self resetScrollers];
  730.     [view scrollRectToVisible:&visibleRect];
  731.     [view display];
  732.     [window reenableFlushWindow];
  733.     [window flushWindow];
  734.     [window makeFirstResponder:view];
  735.     [window setDocEdited:NO];
  736.     NXClose(stream);
  737.     } else {
  738.     if (stream) NXClose(stream);
  739.     Notify("Revert", "I/O error.  Can't revert.");
  740.     }
  741.  
  742.     return self;
  743. }
  744.  
  745. - showTextRuler:sender
  746. /*
  747.  * Sent to cause the Text object ruler to be displayed.
  748.  * Only does anything if the rulers are already visible.
  749.  * This doesn't currently work due to an AppKit bug.
  750.  */
  751. {
  752.     id scrollView = [window contentView];
  753.  
  754.     if ([scrollView verticalRulerIsVisible] && [scrollView horizontalRulerIsVisible]) {
  755.     [scrollView showHorizontalRuler:NO];
  756.     [sender toggleRuler:sender];
  757.     }
  758.  
  759.     return self;
  760. }
  761.  
  762. - hideRuler:sender
  763. /*
  764.  * If sender is nil, we assume the sender wants the
  765.  * ruler hidden, otherwise, we toggle the ruler.
  766.  * If sender is the field editor itself, we do nothing
  767.  * (this allows the field editor to demand that the
  768.  * ruler stay up).
  769.  * This doesn't currently work properly when editing text
  770.  * due to an AppKit bug.
  771.  */
  772. {
  773.     id scrollView = [window contentView];
  774.     id fe = [window getFieldEditor:NO for:NXApp];
  775.  
  776.     if (!sender && [scrollView verticalRulerIsVisible]) {
  777.     [fe toggleRuler:sender];
  778.     [window disableDisplay];
  779.     [scrollView toggleRuler:nil];
  780.     if ([scrollView verticalRulerIsVisible]) [scrollView showHorizontalRuler:YES];
  781.     [window reenableDisplay];
  782.     [scrollView resizeSubviews:(NXSize *)0];
  783.     } else if (sender) {
  784.     [fe toggleRuler:sender];
  785.     if ([scrollView verticalRulerIsVisible]) {
  786.         [scrollView showVerticalRuler:NO];
  787.         [scrollView showHorizontalRuler:NO];
  788.         if (![fe window]) [scrollView toggleRuler:nil];
  789.     } else {
  790.         [scrollView showVerticalRuler:YES];
  791.         if ([fe window]) {
  792.         [scrollView showHorizontalRuler:NO];
  793.         } else {
  794.         [scrollView toggleRuler:nil];
  795.         [scrollView showHorizontalRuler:YES];
  796.         }
  797.     }
  798.     }
  799.  
  800.     return self;
  801. }
  802.  
  803. /* Methods related to naming/saving this document. */
  804.  
  805. - (const char *)filename
  806. /*
  807.  * Gets the fully specified file name of the document.
  808.  * If directory is NULL, then the currentDirectory is used.
  809.  * If name is NULL, then the default title is used.
  810.  */
  811. {
  812.     static char filenamebuf[MAXPATHLEN+1];
  813.  
  814.     if (!directory && !name) {
  815.     [self setName:NULL andDirectory:NULL];
  816.     }
  817.     if (directory) {
  818.     strcpy(filenamebuf, directory);
  819.     strcat(filenamebuf, "/");
  820.     } else {
  821.     filenamebuf[0] = '\0';
  822.     }
  823.     if (name) {
  824.     strcat(filenamebuf, name);
  825.     }
  826.  
  827.     return filenamebuf;
  828. }
  829.  
  830. - (const char *)directory
  831. {
  832.     return directory;
  833. }
  834.  
  835. - (const char *)name
  836. {
  837.     return name;
  838. }
  839.  
  840. - setName:(const char *)newName andDirectory:(const char *)newDirectory
  841. /*
  842.  * Updates the name and directory of the document.
  843.  * newName or newDirectory can be NULL, in which case the name or directory
  844.  * will not be changed (unless one is currently not set, in which case
  845.  * a default name will be used).
  846.  */
  847. {
  848.     char oldName[MAXPATHLEN+1];
  849.     
  850.     if ( hDraw == nil ) {
  851.     hDraw = NXGetNamedObject("HDrawInstance", NXApp);
  852.     }
  853.     if (directory && name) {
  854.     strcpy(oldName, [self filename]);
  855.     } else {
  856.     oldName[0] = '\0';
  857.     }
  858.  
  859.     if ((newName && *newName) || !name) {
  860.      if (!newName || !*newName) newName = "UNTITLED";
  861.     NX_FREE(name);
  862.     name = NXCopyStringBufferFromZone(newName, [self zone]);
  863.     }
  864.  
  865.     if ((newDirectory && (*newDirectory == '/')) || !directory) {
  866.      if (!newDirectory || (*newDirectory != '/')) {
  867.         newDirectory = [hDraw currentDirectory];
  868.     }
  869.     NX_FREE(directory);
  870.     directory = NXCopyStringBufferFromZone(newDirectory, [self zone]);
  871.     }
  872.  
  873.     [window setTitleAsFilename:[self filename]];
  874.     NXNameZone([self zone], [self filename]);
  875.  
  876.     return self;
  877. }
  878.  
  879. - setName:(const char *)file
  880. /*
  881.  * If file is a full path name, then both the name and directory of the
  882.  * document is updated appropriately, otherwise, only the name is changed.
  883.  */
  884. {
  885.     char *lastComponent;
  886.     char path[MAXPATHLEN+1];
  887.  
  888.     if (file) {
  889.     strcpy(path, file);
  890.     lastComponent = strrchr(path, '/');
  891.     if (lastComponent) {
  892.         *lastComponent++ = '\0';
  893.         return [self setName:lastComponent andDirectory:path];
  894.     } else {
  895.         return [self setName:file andDirectory:NULL];
  896.     }
  897.     }
  898.  
  899.     return self;
  900. }
  901.  
  902. - saveTo:(const char *)file using:(SEL)streamWriter
  903. /*
  904.  * Performed by the saveTo: method, this method uses the streamWriter method
  905.  * to have the GraphicView write itself in some foreign format (i.e., not
  906.  * in Draw archive format).  It does some work to make the default name
  907.  * of the file being saved to be the name of the document with the appropriate
  908.  * extension.  It brings up the SavePanel so the user can specify the name
  909.  * of the file to save to.
  910.  */
  911. {
  912.     NXStream *stream;
  913.  
  914.     if (!file || !streamWriter) return self;
  915.  
  916.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  917.     if (stream) {
  918.     [view perform:streamWriter with:(id)stream];
  919.     NXSaveToFile(stream, file);
  920.     NXCloseMemory(stream, NX_FREEBUFFER);
  921.     }
  922.  
  923.     return self;
  924. }
  925.  
  926.  
  927. - save
  928. /*
  929.  * Writes out the document in three steps:
  930.  *
  931.  * 1. Write the printInfo object
  932.  * 2. Write the rectangle of the contentView of the window
  933.  * 3. Write the GraphicView itself.
  934.  *
  935.  * See GraphicView's write: method for more details on how the GraphicView
  936.  * is archived.
  937.  */
  938. {
  939.     id list;
  940.     int version;
  941.     NXTypedStream *ts;
  942.     const char *saveFile = [self filename];
  943.     NXRect windowFrame, contentViewRect;
  944.     char buffer[MAXPATHLEN+1];
  945.  
  946.     if ([view isDirty]) {
  947.     strcpy(buffer, saveFile);
  948.     strcat(buffer, "~");
  949.     unlink(buffer);
  950.     link(saveFile, buffer);
  951.     unlink(saveFile);
  952.     list = [view plotList];
  953.     [list makeObjectsPerform:@selector(changeRefFileNameIfValid:)
  954.                     with:(id)saveFile];
  955.     ts = NXOpenTypedStreamForFile(saveFile, NX_WRITEONLY);
  956.     if (ts) {
  957.         haveSavedDocument = YES;
  958.         version = NEW_DRAW_VERSION;
  959.         NXWriteType(ts, "i", &version);
  960.         NXWriteRootObject(ts, printInfo);
  961.         [window getFrame:&windowFrame];
  962.         [[window class] getContentRect:&contentViewRect
  963.                   forFrameRect:&windowFrame
  964.                      style:[window style]];
  965.         NXWriteRect(ts, &contentViewRect);
  966.         NXWriteRootObject(ts, view);
  967.         NXCloseTypedStream(ts);
  968.     } else {
  969.         Notify("Save", "Can't create file.");
  970.     }
  971.     }
  972.  
  973.     return self;
  974. }
  975.  
  976. - (BOOL)needsSaving
  977. {
  978.     return ([view isDirty] && (haveSavedDocument || ![view isEmpty]));
  979. }
  980.  
  981. /* Window delegate methods. */
  982.  
  983. - windowWillClose:sender action:(const char *)action
  984. /*
  985.  * If the GraphicView has been edited, then this asks the user if she
  986.  * wants to save the changes before closing the window.  When the window
  987.  * is closed, the DrawDocument itself must be freed.  This is accomplished
  988.  * via Application's delayedFree: mechanism.  Unfortunately, by the time
  989.  * delayedFree: frees the DrawDocument, the window and view instance variables
  990.  * will already have automatically been freed by virtue of the window's being
  991.  * closed.  Thus, those instance variables must be set to nil to avoid their
  992.  * being freed twice.
  993.  *
  994.  * Returning nil from this method informs the caller that the window should
  995.  * NOT be closed.  Anything else implies it should be closed.
  996.  */
  997. {
  998.     int save;
  999.  
  1000.     if ([self needsSaving]) {
  1001.     save = NXRunAlertPanel(action, "%s has changes. Save them?", "Save", "Don't Save", "Cancel", name);
  1002.     if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) {
  1003.         return nil;
  1004.     } else {
  1005.         [sender endEditingFor:self];    /* terminate any editing */
  1006.         if (save == NX_ALERTDEFAULT) {
  1007.         [self save:nil];
  1008.         }
  1009.     }
  1010.     }
  1011.  
  1012.     [self unregisterWindow];
  1013.     window = nil;
  1014.     view = nil;
  1015.     [NXApp setPrintInfo:nil];
  1016.     [NXApp delayedFree:self];
  1017.  
  1018.     return self;
  1019. }
  1020.  
  1021. - windowWillClose:sender
  1022. {
  1023.     return [self windowWillClose:sender action:"Close"];
  1024. }
  1025.  
  1026. - windowDidBecomeMain:sender
  1027. /*
  1028.  * Switch the Application's PrintInfo to the document's when the document
  1029.  * window becomes the main window.  Also set the cursor appropriately
  1030.  * depending on which tool is currently selected.
  1031.  */
  1032. {
  1033.     [NXApp setPrintInfo:printInfo];
  1034.     [self resetCursor];
  1035.     return self;
  1036. }
  1037.  
  1038. - windowWillResize:sender toSize:(NXSize *)size
  1039. /*
  1040.  * Constrains the size of the window to never be larger than the
  1041.  * GraphicView inside it (including the ScrollView around it).
  1042.  */
  1043. {
  1044.     NXRect fRect, cRect;
  1045.  
  1046.     getContentSizeForView(view, &cRect.size);
  1047.     [[window class] getFrameRect:&fRect forContentRect:&cRect style:[window style]];
  1048.     if ([[window contentView] horizontalRulerIsVisible]) fRect.size.height += [Ruler width];
  1049.     if ([[window contentView] verticalRulerIsVisible]) fRect.size.width += [Ruler width];
  1050.     size->width = MIN(fRect.size.width, size->width);
  1051.     size->height = MIN(fRect.size.height, size->height);
  1052.     size->width = MAX(MIN_WINDOW_WIDTH, size->width);
  1053.     size->height = MAX(MIN_WINDOW_HEIGHT, size->height);
  1054.  
  1055.     return self;
  1056. }
  1057.  
  1058. - windowWillMiniaturize:sender toMiniwindow:counterpart
  1059. {
  1060.     char *dot;
  1061.     char title[MAXPATHLEN+1];
  1062.  
  1063.     strcpy(title, [self name]);
  1064.     dot = strrchr(title, '.');
  1065.     if (dot && !strcmp(dot, ".hdraw")) *dot = '\0';
  1066.     [counterpart setTitle:title];
  1067.     return self;
  1068. }
  1069.  
  1070. /* Icon dragging methods */
  1071.  
  1072. - registerWindow
  1073. /*
  1074.  * Registers the document window with the Workspace Manager so that when the
  1075.  * user picks up an icon in the Workspace and drags it over our document window
  1076.  * and lets go, iconEntered:... and iconReleasedAt::ok: messages will be
  1077.  * sent to the DrawDocument from the Workspace Manager.  Allows the user to
  1078.  * drag PostScript and TIFF files into the document.
  1079.  */
  1080. {
  1081.     unsigned int windowNum;
  1082.     id speaker = [NXApp appSpeaker];
  1083.  
  1084.     listener = [[Listener allocFromZone:[self zone]] init];
  1085.     [listener setDelegate:self];
  1086.     [listener usePrivatePort];
  1087.     [listener addPort];
  1088.     NXConvertWinNumToGlobal([window windowNum], &windowNum);
  1089.     [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
  1090.     [speaker registerWindow:windowNum toPort:[listener listenPort]];
  1091.  
  1092.     return self;
  1093. }
  1094.  
  1095. - unregisterWindow
  1096. /*
  1097.  * Undoes what registerWindow does.
  1098.  */
  1099. {
  1100.     unsigned int windowNum;
  1101.     id speaker = [NXApp appSpeaker];
  1102.  
  1103.     if (listener) {
  1104.     [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
  1105.     NXConvertWinNumToGlobal([window windowNum], &windowNum);
  1106.     [speaker unregisterWindow:windowNum];
  1107.     [listener free];
  1108.     }
  1109.  
  1110.     return self;
  1111. }
  1112.  
  1113. - (int)iconEntered:(int)windowNum at:(double)x :(double)y
  1114.     iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
  1115.     iconWidth:(double)iconWidth iconHeight:(double)iconHeight
  1116.     pathList:(char *)pathList
  1117. /*
  1118.  * Called whenever an icon is dragged from the Workspace over the document
  1119.  * window.  At this point, all that is done is to salt away the list of files
  1120.  * represented by the icon.  All the real work is done in iconReleasedAt::ok:.
  1121.  */
  1122. {
  1123.     if (!iconPathList || strcmp(iconPathList, pathList)) {
  1124.     NX_FREE(iconPathList);
  1125.     iconPathList = NXCopyStringBufferFromZone(pathList, [self zone]);
  1126.     }
  1127.     return 0;
  1128. }
  1129.  
  1130. - (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
  1131. /*
  1132.  * Goes through the list of files associated with the icon dragged
  1133.  * from the Workspace and checks if any of them are PostScript or TIFF.
  1134.  * If any are, then the GraphicView is asked to load those in as objects.
  1135.  * Very important: an NX_DURING handler is required around all the processing
  1136.  * of this method since an uncaught raised error will cause this method not
  1137.  * to return and thus hang the Workspace Manager for a while.
  1138.  */
  1139. {
  1140.     id        inspectTuple;
  1141.     NXPoint p;
  1142.     volatile int foundOne = NO;
  1143.     char *file, *tab, *extension;
  1144.     BOOL refFlag;
  1145.     int  irc;
  1146.  
  1147.     p.x = x; p.y = y;
  1148.     [window convertScreenToBase:&p];
  1149.     [view convertPoint:&p fromView:nil];
  1150.  
  1151.     NX_DURING
  1152.     file = iconPathList;
  1153.     while (file) {
  1154.         tab = strchr(file, '\t');
  1155.         if (tab) *tab = '\0';
  1156.         extension = strrchr(file, '.');
  1157.         if (extension) {
  1158.         if (!strcmp(extension, ".ps") || !strcmp(extension, ".eps") ||
  1159.             !strcmp(extension,".tiff") || !strcmp(extension,".tif")) {
  1160.             [self loadImageFile:file at:&p
  1161.                   allowAlpha:(strcmp(extension,".tiff") &&
  1162.                               strcmp(extension,".tif"))];
  1163.             foundOne = YES;
  1164.         }
  1165.         if ( !strcmp(extension, ".hippo") ) {
  1166.             if ( hDraw == nil ) {
  1167.             hDraw = NXGetNamedObject("HDrawInstance", NXApp);
  1168.             }
  1169.             inspectTuple = [hDraw inspectTuple];
  1170.             irc = NXRunAlertPanel( "Open",
  1171.                      "Import tuple by reference?",
  1172.                  "Yes", "No", NULL);
  1173.             refFlag = (irc == NX_ALERTDEFAULT) ? YES : NO ;
  1174.             irc = [inspectTuple openTupleFile:file by:refFlag];
  1175.             if ( irc == HD_REPLACE ) {
  1176.             [inspectTuple bindDisplays];
  1177.             }
  1178.             if ( irc != HD_CANCEL ) {
  1179.                     foundOne = YES;
  1180.             }
  1181.         }
  1182.         }
  1183.         file = tab ? ++tab : NULL;
  1184.     }
  1185.     if (foundOne) {
  1186.         [NXApp activateSelf:YES];
  1187.         [window makeKeyAndOrderFront:self];
  1188.     }
  1189.     NX_HANDLER
  1190.     NX_ENDHANDLER
  1191.  
  1192.     *flag = foundOne;
  1193.  
  1194.     return 0;
  1195. }
  1196.  
  1197. /* Validates whether a menu command makes sense now */
  1198.  
  1199. - (BOOL)validateCommand:menuCell
  1200. /*
  1201.  * Validates whether a menu command that DrawDocument responds to
  1202.  * is valid at the current time.
  1203.  */
  1204. {
  1205.     SEL action = [menuCell action];
  1206.  
  1207.     if (action == @selector(save:)) {
  1208.     return [view isDirty];
  1209.     } else if (action == @selector(revertToSaved:)) {
  1210.     return ([view isDirty] && haveSavedDocument);
  1211.     } else if (action == @selector(saveAs:)) {
  1212.     return (haveSavedDocument || ![view isEmpty]);
  1213.     } else if (action == @selector(saveTo:)) {
  1214.     return ![view isEmpty];
  1215.     } else if (action == @selector(close:)) {
  1216.     return YES;
  1217.     } else if (action == @selector(hideRuler:)) {
  1218.     if ([[window contentView] eitherRulerIsVisible] && strcmp([menuCell title], "Hide Ruler")) {
  1219.         [menuCell setTitle:"Hide Ruler"];
  1220.         [menuCell setEnabled:NO];
  1221.     } else if (![[window contentView] eitherRulerIsVisible] && strcmp([menuCell title], "Show Ruler")) {
  1222.         [menuCell setTitle:"Show Ruler"];
  1223.         [menuCell setEnabled:NO];
  1224.     }
  1225.     } else if (action == @selector(alignSelLeft:) ||
  1226.            action == @selector(alignSelRight:) ||
  1227.            action == @selector(alignSelCenter:) ||
  1228.            action == @selector(checkSpelling:) ||
  1229.            action == @selector(showGuessPanel:)) {
  1230.     return [[window getFieldEditor:NO for:NXApp] superview] ? YES : NO;
  1231.     }
  1232.  
  1233.     return YES;
  1234. }
  1235.  
  1236. /* Cursor-setting method */
  1237.  
  1238. - resetCursor
  1239. /*
  1240.  * Sets the document's cursor according to whatever the current graphic is.
  1241.  * Makes the graphic view the first responder if there isn't one or if
  1242.  * no tool is selected (the cursor is the normal one).
  1243.  */
  1244. {
  1245.     id fr, cursor;
  1246.     id scrollview = [window contentView];
  1247.  
  1248.     if ( hDraw == nil ) {
  1249.     hDraw = NXGetNamedObject("HDrawInstance", NXApp);
  1250.     }
  1251.     cursor = [hDraw cursor];
  1252.     [scrollview setDocCursor:cursor];
  1253.     fr = [window firstResponder];
  1254.     if (!fr || fr == window || cursor == NXArrow) {
  1255.     [window makeFirstResponder:view];
  1256.     }
  1257.  
  1258.     return self;
  1259. }
  1260. @end
  1261.  
  1262.